﻿//	Copyright (c) 2013 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Text;
using System.Transactions;

using Ordinaire.Db;
using Ordinaire.Commerce.Db;
using Ordinaire.Commerce.Data;
using Ordinaire.Taxonomy;

namespace Ordinaire.Commerce
{
    /// <summary>
    /// Represents a complete list of good.
    /// </summary>
    public class GoodCatalog : BusinessManager<GoodDataManager, CContext>
    {
        #region Variables

        /// <summary>
        /// The instance of Ordinaire.Taxonomy.Vocabularies.
        /// </summary>
        private Vocabularies vocabularies;

        #endregion

        #region Constructor

        /// <summary>
        /// Returns new instance of Ordinaire.Commerce.GoodCatalogue.
        /// </summary>
        public GoodCatalog()
        {
            this.dataManager  = new GoodDataManager();
            this.vocabularies = new Vocabularies();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Adds new good with the specified name, measurement unit, and code/barcode.
        /// </summary>
        /// <param name="name">the good name</param>
        /// <param name="code">the code/barcode of the good</param>
        /// <param name="unit">the measurement unit</param>
        /// <param name="value">the value/price of the good</param>
        /// <returns>the new good with the newly assigned id</returns>
        public Good Add(string name, string code = null, string unit = null, decimal value = (decimal) 0)
        {
            name.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "name"));
            
            return Add(name, code, unit.ToVocabId<MeasureUnits>(), value);
        }

        /// <summary>
        /// Adds new good with the specified name, measurement unit, and code/barcode.
        /// </summary>
        /// <param name="name">the good name</param>
        /// <param name="code">the code/barcode of the good</param>
        /// <param name="unitId">the measurement unit id</param>
        /// <param name="value">the value/price of the good</param>
        /// <returns>the new good with the newly assigned id</returns>
        public Good Add(string name, string code = null, int unitId = 0, decimal value = (decimal) 0)
        {
            name.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "name"));
            
            return dataManager.Insert(name, code, unitId, value).ToGood();
        }

        /// <summary>
        /// Adds new composition good id to the specified good id.
        /// </summary>
        /// <param name="id">the good id with the specified composition</param>
        /// <param name="compositionId">the good id of the composition</param>
        /// <param name="quantity">the quantity of the composition</param>
        public void AddComposition(int id, int compositionId, int quantity = 1)
        {
            id.ThrowIfNotExists<ArgumentException, int>(dataManager, String.Format("id '{0}'", id));
            compositionId.ThrowIfNotExists<ArgumentException, int>(dataManager, String.Format("composition Id '{0}'", compositionId));

            HelpAddNewComposition(id, compositionId, quantity);
        }

        /// <summary>
        /// Adds new composition by its code to the specified good id.
        /// </summary>
        /// <param name="id">the good if with the specified composition</param>
        /// <param name="compositionCode">the good code of the composition</param>
        /// <param name="quantity">the quantity of the composition</param>
        public void AddCompositionByCode(int id, string compositionCode, int quantity = 1)
        {
            id.ThrowIfNotExists<ArgumentException, int>(dataManager, String.Format("id '{0}'", id));

            int compositionId = dataManager.GetIdByCode(compositionCode);
            compositionId.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.NotExists, String.Format("composition code '{0}'", compositionCode)));

            HelpAddNewComposition(id, compositionId, quantity);
        }

        /// <summary>
        /// Adds new composition by its name to the specified good id.
        /// </summary>
        /// <param name="id">the good if with the specified composition</param>
        /// <param name="compositionName">the good name of the composition</param>
        /// <param name="quantity">the quantity of the composition</param>
        public void AddCompositionByName(int id, string compositionName, int quantity = 1)
        {
            id.ThrowIfNotExists<ArgumentException, int>(dataManager, String.Format("id '{0}'", id));

            int compositionId = dataManager.GetIdByName(compositionName);
            compositionId.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.NotExists, String.Format("composition name '{0}'", compositionName)));

            HelpAddNewComposition(id, compositionId, quantity);
        }

        /// <summary>
        /// Creates and adds new composition into the specified good id.
        /// </summary>
        /// <param name="id">the good with the specified composition</param>
        /// <param name="compositionName">the good name of the composition</param>
        /// <param name="compositionCode">the code of the composition good</param>
        /// <param name="quantity">the quantity of the composition</param>
        /// <param name="unit">the measurement unit of the composition good</param>
        /// <returns>the new composition good</returns>
        public Good AddNewComposition(int id, string compositionName, string compositionCode = null, int quantity = 1, string unit = null)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("goodId '{0}'", id)));
            
            // Make sure the name (and code) of new composition good doesn't exist yet
            if (!String.IsNullOrEmpty(compositionCode)) // Make sure specified code doesn't exist yet.
            {
                // Code already exists
                if (dataManager.ExistsByCode(compositionCode))
                {
                    throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.AlreadyExists, String.Format("composition code '{0}'", compositionCode));
                }
            }
            else // Make sure specified name doesn't exist yet.
            {
                compositionName.ThrowIfExistsByName<ArgumentException>(dataManager, String.Format("composition name '{0}'", compositionName));
            }

            int unitId = unit.ToVocabId<MeasureUnits>();
            var good = compositionCode.ToGoodByCode(compositionName, unitId);
            if (good == null)
            {
                return null;
            }

            AddComposition(id, good.Id, quantity);
            return good;
        }

        /// <summary>
        /// Returns a good with the specified id.
        /// </summary>
        /// <param name="id">the good id</param>
        /// <returns>the good with the specified id</returns>
        public Good Get(int id)
        {
            id.ThrowIfEmpty<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("id '{0}'", id)));
            Goods goodData = dataManager.Get(id);
            if (goodData == null)
            {
                return null;
            }

            Good good = goodData.ToGood();
            return good;
        }

        /// <summary>
        /// Returns a good with the specified code/barcode.
        /// </summary>
        /// <param name="code">the code/barcode</param>
        /// <returns>the good with the specified code/barcode</returns>
        public Good GetByCode(string code)
        {
            code.ThrowIfNullOrEmpty<ArgumentNullException>(String.Format(ExceptionMessage.IsNullOrEmpty, "code"));
            Goods goodData = dataManager.GetByCode(code);
            if (goodData == null)
            {
                return null;
            }

            Good good = goodData.ToGood();
            return good;
        }

        /// <summary>
        /// Returns a good with the specified name.
        /// </summary>
        /// <param name="name">the good name</param>
        /// <returns>the good with the specified name</returns>
        public Good GetByName(string name)
        {
            name.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "name"));
            Goods goodData = dataManager.GetByName(name);
            if ( goodData == null)
            {
                return null;
            }

            Good good = goodData.ToGood();
            return good;
        }

        /// <summary>
        /// Returns list of compositions with the specified good id, if any.
        /// </summary>
        /// <param name="id">the good id</param>
        /// <returns>list of compositions, if any; otherwise empty list</returns>
        public List<GoodComposition> GetGoodCompositions(int id)
        {
            id.ThrowIfNotExists<ArgumentException, int>(dataManager, String.Format("id '{0}'", id));
            return HelpGetGoodCompositions(id);
        }

        /// <summary>
        /// Returns list of compositions with the specified good code.
        /// </summary>
        /// <param name="code">the good code</param>
        /// <returns>list of compositions, if any; otherwise empty list</returns>
        public List<GoodComposition> GetGoodCompositionsByCode(string code)
        {
            code.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "code"));
            Good good = GetByCode(code);
            if (good == null)
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.NotExists, String.Format("code '{0}'", code));
            }

            return HelpGetGoodCompositions(good.Id);
        }

        #endregion

        #region Protected methods

        /// <summary>
        /// Adds new composition data.
        /// </summary>
        /// <param name="id">the good id</param>
        /// <param name="compositionId">the composition id</param>
        /// <param name="quantity">the quantity of the composition</param>
        protected void HelpAddNewComposition(int id, int compositionId, int quantity = 1)
        {
            CompositeGoods newCompositeData = new CompositeGoods();
            newCompositeData.Id             = compositionId;
            newCompositeData.ParentId       = id;
            newCompositeData.Quantity       = quantity;

            dataManager.Insert<CompositeGoods>(newCompositeData);
        }
        
        /// <summary>
        /// Returns list of compositions of the specified good id, if any.
        /// </summary>
        /// <param name="id">the good id</param>
        /// <returns>list of compositions, if any; otherwise empty list</returns>
        protected List<GoodComposition> HelpGetGoodCompositions(int id)
        {
            List<GoodComposition> compositions = new List<GoodComposition>();

            List<CompositeGoods> compositeData = dataManager.GetChildren<CompositeGoods>(id);
            if (compositeData == null || compositeData.Count < 1)
            {
                return compositions;
            }

            GoodComposition good   = null;
            CompositeGoods  record = null;
            for (int i = 0; i < compositeData.Count; i++)
            {
                record = compositeData[i];

                good = record.ToGoodComposition();
                good.IsComposite = HelpIsComposite(record.Id);

                compositions.Add(good);
            }

            return compositions;
        }

        /// <summary>
        /// Returns value indicating whether good with the specified id is a composite good.
        /// </summary>
        /// <param name="id">the good id</param>
        /// <returns>true if composite good; otherwise false</returns>
        protected bool HelpIsComposite(int id)
        {
            return dataManager.CountChildren<CompositeGoods>(id) > 0;
        }

        #endregion
    }

    /// <summary>
    /// Contains extension methods related to GoodCatalog.
    /// </summary>
    public static class GoodCatalogExtension
    {
        /// <summary>
        /// Retrieves the corresponding good detail of specified code if exists.
        /// Otherwise creates new good detail with specified code, name, unit, and value per unit.
        /// </summary>
        /// <param name="code">the good code</param>
        /// <param name="name">the good name</param>
        /// <param name="unit">the measurement unit</param>
        /// <param name="valuePerUnit">value of good per unit</param>
        /// <returns>the good</returns>
        public static Good ToGoodByCode(this string code, string name, int unitId = 0, decimal valuePerUnit = (decimal) 0)
        {
            if (String.IsNullOrEmpty(code))
            {
                return null;
            }

            var catalog = new GoodCatalog();
            var good = catalog.GetByCode(code);
            if (good == null)
            {
                good = catalog.Add(name, code, unitId, valuePerUnit);
                if (good == null)
                {
                    ExceptionFactory.Create<OException>(ExceptionMessage.Failure, "good creation");
                }
            }
            return good;
        }

        /// <summary>
        /// Retrieves the id of corresponding good detail of specified code if exists.
        /// Otherwise creates new good detail with specified code, name, unit, and value per unit.
        /// </summary>
        /// <param name="code">the good code</param>
        /// <param name="name">the good name</param>
        /// <param name="unit">the measurement unit</param>
        /// <param name="valuePerUnit">value of good per unit</param>
        /// <returns>id of the good</returns>
        public static int ToGoodIdByCode(this string code, string name, string unit = null, decimal valuePerUnit = (decimal) 0)
        {
            return code.ToGoodIdByCode(name, unit.ToVocabId<MeasureUnits>(), valuePerUnit);
        }

        /// <summary>
        /// Retrieves the id of corresponding good detail of specified code if exists.
        /// Otherwise creates new good detail with specified code, name, unit, and value per unit.
        /// </summary>
        /// <param name="code">the good code</param>
        /// <param name="name">the good name</param>
        /// <param name="unitId">the measurement unit id</param>
        /// <param name="valuePerUnit">value of good per unit</param>
        /// <returns>id of the good</returns>
        public static int ToGoodIdByCode(this string code, string name, int unitId = 0, decimal valuePerUnit = (decimal) 0)
        {
            var good = code.ToGoodByCode(name, unitId, valuePerUnit);
            if (good == null)
            {
                return 0;
            }
            return good.Id;
        }
    }
}
